package br.com.pucrs.aa;

import jason.asSyntax.ASSyntax;
import jason.asSyntax.Atom;
import jason.asSyntax.Literal;
import jason.asSyntax.Structure;
import jason.asSyntax.Term;
import jason.environment.TimeSteppedEnvironment;
import jason.environment.grid.Location;

import java.util.logging.Level;
import java.util.logging.Logger;

public class StreetEnv extends TimeSteppedEnvironment {

	public static Logger logger = Logger.getLogger(StreetEnv.class.getName());

	private StreetModel model;
	private StreetView view;

	private String agWalkerClassName = "walker";
	private String agLittleBlueClassName = "littleBlue";

	private Term moveUp = Literal.parseLiteral("move(up)");
	private Term moveLeft = Literal.parseLiteral("move(left)");
	private Term moveDown = Literal.parseLiteral("move(down)");
	private Term moveRight = Literal.parseLiteral("move(right)");
	private Term semaphoreLeftRight = Literal.parseLiteral("change_semaphore(left,right)");
	private Term semaphoreUpDown = Literal.parseLiteral("change_semaphore(up,down)");

	public static Atom anOBSTACLE = new Atom("obstacle");
	public static Atom aWALKER = new Atom("walker");
	public static Atom aEMPTY = new Atom("empty");
	public static Atom aSTOP = new Atom("stop");

	public int getAgIdFromName(String agName) {
		if (agName.startsWith(agWalkerClassName)) {
			if (model.getNumberOfWalkers() == 1) {
				return 0; //walkers are added first
			} else {
				return (Integer.parseInt(agName.substring(agWalkerClassName.length())) - 1);
			}
		} else if (agName.startsWith(agLittleBlueClassName)) {
			//coordinators are added after walkers
			int idAfterWalkers = model.getNumberOfWalkers();
			if (model.getNumberOfCoordinators() == 1) {
				return idAfterWalkers;
			} else {
				return (idAfterWalkers + (Integer.parseInt(agName.substring(agLittleBlueClassName.length())) - 1));
			}
		}
		logger.warning("No agent with name :" + agName);
		return -1;
	}

	public String getAgName(int agId) {
		//the first are walkers
		boolean agIsWalker = agId < model.getNumberOfWalkers();
		if (agIsWalker) {
			return agWalkerClassName + (agId + 1);
		} else {
			return agLittleBlueClassName + (agId - model.getNumberOfWalkers() + 1);
		}
	}

	@Override
	public void init(String[] args) {
		try {
			this.model = StreetFactory.loadStreet();
			super.init(new String[] { "1000" }); //set step timeout
			this.updateNumberOfAgents();
			this.clearPercepts();
			this.view = new StreetView(model);
			this.model.setView(view);
		} catch (Exception e) {
			logger.warning("Error creating world " + e);
		}
	}

	@Override
	protected void updateAgsPercept() {
		for (int i = 0; i < model.getNbOfAgs(); i++) {
			this.updateAgPercept(i);
		}
	}

	private void updateAgPercept(int agId) {
		this.updateAgPercept(getAgName(agId), agId);
	}

	private void updateAgPercept(String agName, int agId) {
		this.clearPercepts(agName);
		boolean agIsWalker = agId < model.getNumberOfWalkers();

		if (agIsWalker) {
			//walkers must know their street
			Street agStreet = model.getAgStreet().get(agId);
			this.addPercept(getAgName(agId), createAtStreetPerception(new Atom(agStreet.getDir().name().toLowerCase())));

			// its location
			Location l = model.getAgPos(agId);
			Literal p = ASSyntax.createLiteral("pos", ASSyntax.createNumber(l.x), ASSyntax.createNumber(l.y), ASSyntax.createNumber(getStep()));
			this.addPercept(agName, p);

			//what's around
			this.updateAgPercept(agName, agId, l.x - 1, l.y - 1);
			this.updateAgPercept(agName, agId, l.x - 1, l.y);
			this.updateAgPercept(agName, agId, l.x - 1, l.y + 1);
			this.updateAgPercept(agName, agId, l.x, l.y - 1);
			this.updateAgPercept(agName, agId, l.x, l.y);
			this.updateAgPercept(agName, agId, l.x, l.y + 1);
			this.updateAgPercept(agName, agId, l.x + 1, l.y - 1);
			this.updateAgPercept(agName, agId, l.x + 1, l.y);
			this.updateAgPercept(agName, agId, l.x + 1, l.y + 1);
		} else {
			for (Location location : model.getCoordinatorArea().get(agId)) {
				//coordinators must know their area
				this.addPercept(agName, createQuadPerception(location));
				//update his perceptions in his area
				this.updateAgPercept(agName, agId, location.x, location.y);
				//update step perceptions (used to change semaphore signal)
				Literal currentStep = ASSyntax.createLiteral("step", ASSyntax.createNumber(getStep()));
				this.addPercept(agName, currentStep);
			}
		}
	}

	private void updateAgPercept(String agName, int agId, int x, int y) {
		if (model == null || !model.inGrid(x, y))
			return;
		boolean isEmpty = true;
		if (model.hasObject(StreetModel.OBSTACLE, x, y)) {
			addPercept(agName, createStreetCellPerception(x, y, anOBSTACLE));
			isEmpty = false;
		} else {
			if (model.hasObject(StreetModel.STOP, x, y)) {
				addPercept(agName, createStreetCellPerception(x, y, aSTOP));
				isEmpty = false;
			}
			int otherag = model.getAgAtPos(x, y);
			if (otherag >= 0 && otherag != agId) {
				isEmpty = false;
				addPercept(agName, createStreetCellPerception(x, y, aWALKER));
			}
		}
		if (isEmpty)
			addPercept(agName, createStreetCellPerception(x, y, aEMPTY));
	}

	public static Literal createStreetCellPerception(int x, int y, Atom obj) {
		return ASSyntax.createLiteral("street_cell", ASSyntax.createNumber(x), ASSyntax.createNumber(y), obj);
	}

	public static Literal createAtStreetPerception(Atom obj) {
		return ASSyntax.createLiteral("at_street", obj);
	}

	public static Literal createQuadPerception(Location location) {
		return ASSyntax.createLiteral("responsable_quad", ASSyntax.createNumber(location.x), ASSyntax.createNumber(location.y));
	}

	@Override
	protected void updateNumberOfAgents() {
		setNbAgs(model.getNbOfAgs());
	}

	@Override
	public boolean executeAction(String ag, Structure action) {
		boolean result = false;
		int agId = getAgIdFromName(ag);
		try {
			//get the agent id based on its name
			if (agId != -1) {
				if (action.equals(moveUp)) {
					result = model.move(Direction.UP, agId);
				} else if (action.equals(moveDown)) {
					result = model.move(Direction.DOWN, agId);
				} else if (action.equals(moveLeft)) {
					result = model.move(Direction.LEFT, agId);
				} else if (action.equals(moveRight)) {
					result = model.move(Direction.RIGHT, agId);
				} else if (action.equals(semaphoreLeftRight)) {
					result = model.changeSemaphore(Direction.LEFT, Direction.RIGHT, agId);
				} else if (action.equals(semaphoreUpDown)) {
					result = model.changeSemaphore(Direction.UP, Direction.DOWN, agId);
				} else {
					logger.warning("executing: " + action + ", but not implemented!");
				}
			}
		} catch (Exception e) {
			logger.log(Level.SEVERE, "error executing " + action + " for " + ag + " (ag code:" + agId + ")", e);
		}

		return result;
	}

}
